using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Xml.Linq;
using Caliburn.Micro;
using MediaOwl.LastFm.Model;

namespace MediaOwl.LastFm.Data
{
    /// <summary>
    /// This <see cref="IResult"/> asyncronously loads XmlData.
    /// </summary>
    public class LoadDataResult<T> : IResult
        where T : EntityBase, new()
    {
        private readonly string searchString;
        private readonly ILastFmDataRepository repository;
        private readonly bool isSearch;

        private readonly Action<IEnumerable<T>, Search> onSuccessWithSearch;
        private readonly Action<IEnumerable<T>> onSuccess;
        private readonly Action<Exception> onFail;

        /// <summary>
        /// The constructor
        /// </summary>
        public LoadDataResult(string searchString, ILastFmDataRepository repository, Action<IEnumerable<T>, Search> onSuccessWithSearch, Action<Exception> onFail)
        {
            this.searchString = searchString;
            this.onFail = onFail;
            this.onSuccessWithSearch = onSuccessWithSearch;
            this.repository = repository;

            isSearch = true;
        }

        public LoadDataResult(string searchString, ILastFmDataRepository repository, Action<IEnumerable<T>> onSuccess, Action<Exception> onFail)
        {
            this.searchString = searchString;
            this.onFail = onFail;
            this.onSuccess = onSuccess;
            this.repository = repository;

            isSearch = false;
        }

        #region Implementation of IResult

        /// <summary>
        /// Executes the result using the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute(ActionExecutionContext context)
        {
            if (string.IsNullOrEmpty(LastFmDataAccess.ApiKey))
            {
                var args = new ResultCompletionEventArgs
                {
                    Error = new Exception("Can't execute query. No API-Key found."),
                    WasCancelled = false
                };
                if (onFail != null)
                    Caliburn.Micro.Execute.OnUIThread(() => onFail(args.Error));
                Completed(this, args);
                return;
            }

            var webClient = new WebClient();

            webClient.OpenReadCompleted += (sender, e) =>
            {
                if (e.Error == null)
                {
                    XDocument doc = XDocument.Load(e.Result);
                    XElement errorElement = doc.Descendants("error").FirstOrDefault();

                    if (errorElement != null)
                    {
                        if (onFail != null)
                            Caliburn.Micro.Execute.OnUIThread(() => onFail(e.Error));

                        Completed(this, new ResultCompletionEventArgs
                        {
                            Error = new Exception(errorElement.Value),
                            WasCancelled = false
                        });
                        return;
                    }


                    if (doc.Root == null)
                    {
                        if (onFail != null)
                            Caliburn.Micro.Execute.OnUIThread(() => onFail(e.Error));

                        Completed(this, new ResultCompletionEventArgs
                        {
                            Error = new Exception("Error: RootElement is null;"),
                            WasCancelled = false
                        });
                        return;
                    }

                    Search search = null;

                    if (isSearch)
                        search = repository.SetOrCreateSearch<T>(doc.Root);

                    var entities = new List<T>();

                    var elements = doc.Root.Descendants(Helper.GetNodeName<T>());
                    foreach (var xElement in elements)
                    {
                        var newEntity = new T();
                        newEntity.FromXml(xElement);
                        entities.Add(newEntity);
                    }

                    if (isSearch)
                    {
                        if (onSuccessWithSearch != null)
                            Caliburn.Micro.Execute.OnUIThread(() =>
                                                              onSuccessWithSearch(entities, search));
                    }
                    else
                    {
                        if (onSuccess != null)
                            Caliburn.Micro.Execute.OnUIThread(() =>
                                                              onSuccess(entities));
                    }

                    Completed(this, new ResultCompletionEventArgs());
                }
                var args = new ResultCompletionEventArgs { Error = e.Error, WasCancelled = false };
                Completed(this, args);
            };
            webClient.OpenReadAsync(new Uri(searchString));
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

        #endregion
    }
}